﻿using System;
using System.ComponentModel;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Drawing;
using System.Text;
using System.Configuration;

using BizElements.BusinessLayer;

namespace BizElements.Web
{
    // All bound templates must guarantee that IBoundControl controls are used.
    // BoundDataGrid logic depends on that assumption.

    /// <summary>
    /// Base class for all <see cref="DataGrid"/> templates which create <see cref="IBoundControl"/> controls.
    /// </summary>
    /// <remarks>All bound templates must guarantee that <see cref="BizElements.Web.IBoundControl"/> controls are 
    /// used. <see cref="BizElements.Web.BoundDataGrid"/> logic depends on that assumption.</remarks>
    public abstract class BoundTemplateBase : ITemplate
    {
        #region Fields.

        IBindableField boundField;
        IMessageFormatter formatter;
        string cssClass;
        Unit width;
        FieldAccessMode accessMode = FieldAccessMode.Automatic;
        string readOnlyCss;
        CssStyleCollection style = new Label().Style;
        string skinID;

        #endregion

        #region Constructors.

        /// <summary>
        /// Initializes a new instance of the <see cref="BoundTemplateBase"/> class.
        /// </summary>
        public BoundTemplateBase()
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="BoundTemplateBase"/> class.
        /// </summary>
        /// <param name="field"><see cref="IBindableField"/> that contains a reference and meta-data for a bound business class property.</param>
        /// <param name="formatter">The <see cref="IMessageFormatter"/> object used to format bound values.</param>
        public BoundTemplateBase(IBindableField field, IMessageFormatter formatter)
        {
            this.boundField = field;
            this.formatter = formatter;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="BoundTemplateBase"/> class.
        /// </summary>
        /// <param name="field"><see cref="IBindableField"/> that contains a reference and meta-data for a bound business class property.</param>
        /// <param name="formatter">The <see cref="IMessageFormatter"/> object used to format bound values.</param>
        /// <param name="skinID">The skin to apply to the controls generated by this template.</param>
        public BoundTemplateBase(IBindableField field, IMessageFormatter formatter, string skinID)
        {
            this.boundField = field;
            this.formatter = formatter;
            this.skinID = skinID;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="BoundTemplateBase"/> class.
        /// </summary>
        /// <param name="field"><see cref="IBindableField"/> that contains a reference and meta-data for a bound business class property.</param>
        /// <param name="formatter">The <see cref="IMessageFormatter"/> object used to format bound values.</param>
        /// <param name="width">The width of the control created by the template.</param>
        /// <param name="cssClass">CSS Class name applied to controls created by the template.</param>
        /// <param name="readOnlyCss">CSS Class name applied to created controls when in read-only mode.</param>
        public BoundTemplateBase(IBindableField field, IMessageFormatter formatter, int width, string cssClass, string readOnlyCss)
        {
            this.boundField = field;
            this.formatter = formatter;
            this.width = width;
            this.cssClass = cssClass;
            this.readOnlyCss = readOnlyCss;
        }

        #endregion

        #region Methods.

        /// <summary>
        /// Defines the <see cref="DataGrid"/> that child controls and the template belong to.
        /// </summary>
        /// <param name="container">The <b>TableCell</b> which contains the controls instantiated by this template.</param>
        public abstract void InstantiateIn(Control container);

        /// <summary>
        /// Gets the value stored in the <see cref="IBoundControl"/> control created by this template.
        /// </summary>
        /// <param name="controlCreatedByThisTemplate">The control created by this template.</param>
        /// <param name="errorCode">Error encountered while parsing the value.</param>
        /// <returns>The value stored in the control <b>Null</b> if the control doesn't contiain a valid value.</returns>
        public object GetValue(IBoundControl controlCreatedByThisTemplate, out ParseError errorCode)
        {
            var twoWayCtlProperty = ControlPropertyBinderFactory.CreateBindableControlProperty(controlCreatedByThisTemplate);
            return twoWayCtlProperty.GetValue(controlCreatedByThisTemplate, controlCreatedByThisTemplate.TwoWayBoundProperty, this.boundField.DataType, this.formatter, out errorCode);
        }

        /// <summary>
        /// Applies CSS class and width to a <see cref="WebControl"/>.
        /// </summary>
        /// <param name="control">Control to which the style is applied.</param>
        protected void ApplyStyle(WebControl control)
        {
            if (this.cssClass != null)
                control.CssClass = this.cssClass;
            if (this.width.Value > 0)
                control.Width = this.width;

            if (this.style != null && this.style.Count > 0)
            {
                foreach (string cssAttribute in this.style.Keys)
                    control.Style[cssAttribute] = this.style[cssAttribute];
            }
        }

        /// <summary>
        /// Automatically configures access mode for ASP.NET controls which implement <see cref="IBoundControl"/> interface.
        /// </summary>
        /// <param name="boundComponent">Control created by current template and bound to a <see cref="IBindableField"/>.</param>
        /// <returns><b>true</b> if the control is configured for read-only mode; <b>false</b> if edit mode is used.</returns>
        /// <remarks>The access mode is configured based on the following conditions:
        /// <list type="bullet">
        /// <item>If template's <see cref="AccessMode"/> property is set to <see cref="FieldAccessMode.Edit"/> then the control is rendered in edit mode.</item>
        /// <item>If template's <see cref="AccessMode"/> property is set to <see cref="FieldAccessMode.ReadOnly"/> then the control is rendered in read-only mode.</item>
        /// <item>If template's <see cref="AccessMode"/> property is set to <see cref="FieldAccessMode.Automatic"/> then the
        /// method inspects the object and field bound to the generated control. If either field's <see cref="IBindableField.IsReadOnly"/>
        /// of objects <see cref="IValueObject.IsReadOnly"/> are set to <b>true</b> then the control is rendered in edit mode.</item>
        /// </list>
        /// </remarks>
        protected bool ConfigureAccessMode(IBoundControl boundComponent)
        {
            Control aspControl = (Control)boundComponent;

            bool isReadOnly = false;
            bool templateModeIsOverridenInControl = (boundComponent.AccessMode != FieldAccessMode.Automatic);
            FieldAccessMode controlMode = (templateModeIsOverridenInControl) ? boundComponent.AccessMode : this.AccessMode;
            switch (controlMode)
            {
                case FieldAccessMode.Automatic:
                    if (this.BoundField.IsReadOnly)
                        isReadOnly = true;

                    if (!isReadOnly)
                    {
                        IValueObject bizObject = ((DataGridItem)aspControl.NamingContainer).DataItem as IValueObject;
                        if (bizObject != null && bizObject.IsReadOnly)
                            isReadOnly = true;
                    }

                    if (isReadOnly)
                        boundComponent.AccessMode = FieldAccessMode.ReadOnly;

                    break;

                default:
                    if (!templateModeIsOverridenInControl)
                        boundComponent.AccessMode = this.AccessMode;

                    isReadOnly = (this.AccessMode == FieldAccessMode.ReadOnly);
                    break;
            }

            return isReadOnly;
        }

        #endregion

        #region Properties.

        /// <summary>
        /// Gets or sets the field to which the control created by this template is bound.
        /// </summary>
        public IBindableField BoundField
        {
            get { return this.boundField; }
            set { this.boundField = value; }
        }

        /// <summary>
        /// Gets or sets the <see cref="IMessageFormatter"/> object used to format bound values.
        /// </summary>
        public IMessageFormatter Formatter
        {
            get { return this.formatter; }
            set { this.formatter = value; }
        }

        /// <summary>
        /// Gets or sets the CSS Class name applied to controls created by the template.
        /// </summary>
        public string CssClass
        {
            get { return this.cssClass; }
            set { this.cssClass = value; }
        }

        /// <summary>
        /// Gets or sets the width of the control used in the template.
        /// </summary>
        public Unit Width
        {
            get { return this.width; }
            set { this.width = value; }
        }

        /// <summary>
        /// Gets or sets the value which indicates whether the control created by the template can change data in the bound field.
        /// </summary>
        /// <remarks>Default value is <b>FieldAccessMode.Automatic</b>.</remarks>
        public FieldAccessMode AccessMode
        {
            get { return this.accessMode; }
            set { this.accessMode = value; }
        }

        /// <summary>
        /// Gets or set the CSS Class name applied to controls created by the template when in read-only mode.
        /// </summary>
        public string ReadOnlyCss
        {
            get { return this.readOnlyCss; }
            set { this.readOnlyCss = value; }
        }

        /// <summary>
        /// Gets a collection of text attributes that will be rendered as a style attribute on the outer tag of 
        /// the Web server controls created by this template.
        /// </summary>
        public CssStyleCollection Style
        {
            get { return this.style; }
        }

        /// <summary>
        /// Gets or sets the skin to apply to the controls generated by this template.
        /// </summary>
        public string SkinID
        {
            get { return this.skinID; }
            set { this.skinID = value; }
        }

        #endregion
    }
}